package com.lipaluma.test.db.listeners;

import java.lang.reflect.AnnotatedElement;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;

import com.lipaluma.test.db.annotations.LoadDataInfile;
import com.lipaluma.test.db.annotations.assertions.AssertColumns;
import com.lipaluma.test.db.annotations.assertions.AssertDefinedColumns;
import com.lipaluma.test.db.annotations.batch.Batch;
import com.lipaluma.test.db.annotations.clean.CleanAfterManually;
import com.lipaluma.test.db.annotations.clean.CleanSchema;
import com.lipaluma.test.db.annotations.clean.CleanTables;
import com.lipaluma.test.db.internal.Context;
import com.lipaluma.test.db.internal.assertions.AssertColumnUtils;
import com.lipaluma.test.db.internal.batch.BatchUtils;
import com.lipaluma.test.db.internal.clean.CleanUtils;
import com.lipaluma.test.db.internal.loadData.LoadDataUtils;

public class DBTestExecutionListener implements TestExecutionListener {
	private static Logger LOG = LoggerFactory.getLogger(DBTestExecutionListener.class);
	
	@Override
	public void beforeTestClass(TestContext context) throws Exception {
		Context.init(context.getApplicationContext(), context);
//		CopyUtils.copyDb(context.getTestClass().getAnnotation(CopyDbByCommandFrom.class));
//		CopyUtils.copySchema(context.getTestClass().getAnnotation(CopySchema.class));

		cleanBeforeTest(context.getTestClass());
		doBatch(context.getTestClass());
		doLoadDataInfile(context.getTestClass());
	}

	@Override
	public void prepareTestInstance(TestContext context) throws Exception {
		AssertColumnUtils.resetKeys();
		CleanUtils.initParams();
	}

	@Override
	public void beforeTestMethod(TestContext context) throws Exception {
		cleanBeforeTest(context.getTestMethod());
		doBatch(context.getTestMethod());
		doLoadDataInfile(context.getTestMethod());
	}

	@Override
	public void afterTestMethod(TestContext context) throws Exception {
		if(context.getTestException() == null) {
			doAssertion(context.getTestMethod());
		}
		
		cleanAfterTest(context.getTestMethod());
		
		if(context.getTestException() != null) {
			if(Error.class.isAssignableFrom(context.getTestException().getClass()))
					throw (AssertionError)context.getTestException();
			else
				throw (Exception)context.getTestException();
		}
	}

	@Override
	public void afterTestClass(TestContext context) throws Exception {
		cleanAfterTest(context.getTestClass());
	}

	
	private void cleanBeforeTest(AnnotatedElement element) throws Exception {
		CleanSchema cleanSchema = element.getAnnotation(CleanSchema.class);
		if(cleanSchema != null && cleanSchema.before())
			CleanUtils.doClean(cleanSchema);
		CleanSchema.List cleanSchemaList = element.getAnnotation(CleanSchema.List.class);
		if(cleanSchemaList != null) {
			for(CleanSchema clean : cleanSchemaList.value())
				if(clean.before())
					CleanUtils.doClean(clean);
		}

		CleanTables cleanTables = element.getAnnotation(CleanTables.class);
		if(cleanTables != null && cleanTables.before())
			CleanUtils.doClean(cleanTables);
		CleanTables.List cleanTablesList = element.getAnnotation(CleanTables.List.class);
		if(cleanTablesList != null) {
			for(CleanTables clean : cleanTablesList.value())
				if(clean.before())
					CleanUtils.doClean(clean);
		}
	}

	private void cleanAfterTest(AnnotatedElement element) throws Exception {
		CleanSchema cleanSchema = element.getAnnotation(CleanSchema.class);
		if(cleanSchema != null && cleanSchema.after())
			CleanUtils.doClean(cleanSchema);
		CleanSchema.List cleanSchemaList = element.getAnnotation(CleanSchema.List.class);
		if(cleanSchemaList != null) {
			for(CleanSchema clean : cleanSchemaList.value())
				if(clean.after())
					CleanUtils.doClean(clean);
		}

		CleanTables cleanTables = element.getAnnotation(CleanTables.class);
		if(cleanTables != null && cleanTables.after())
			CleanUtils.doClean(cleanTables);
		CleanTables.List cleanTablesList = element.getAnnotation(CleanTables.List.class);
		if(cleanTablesList != null) {
			for(CleanTables clean : cleanTablesList.value())
				if(clean.after())
					CleanUtils.doClean(clean);
		}
		
		CleanUtils.doCleanAfterManually(element.getAnnotation(CleanAfterManually.class));
		CleanUtils.doCleanAfterManually(element.getAnnotation(CleanAfterManually.List.class));
	}
	
	private void doBatch(AnnotatedElement element) throws Exception {
		Batch batch = element.getAnnotation(Batch.class);
		BatchUtils.doBatches(batch);
		Batch.List batches = element.getAnnotation(Batch.List.class);
		BatchUtils.doBatches(batches);
	}

	private void doLoadDataInfile(AnnotatedElement element) throws Exception {
		LoadDataInfile loadData = element.getAnnotation(LoadDataInfile.class);
		LoadDataUtils.doLoadDataInfile(loadData);
		LoadDataInfile.List loadDataList = element.getAnnotation(LoadDataInfile.List.class);
		LoadDataUtils.doLoadDataInfile(loadDataList);
	}


	private void doAssertion(AnnotatedElement element) throws Exception {
		AssertColumns assertColumn = element.getAnnotation(AssertColumns.class);
		AssertColumnUtils.doAssertion(assertColumn);
		AssertColumns.List assertColumnsList = element.getAnnotation(AssertColumns.List.class);
		AssertColumnUtils.doAssertion(assertColumnsList);
		AssertDefinedColumns assertDefinedColumns = element.getAnnotation(AssertDefinedColumns.class);
		AssertColumnUtils.doAssertion(assertDefinedColumns);
		AssertDefinedColumns.List assertDefinedColumnsList = element.getAnnotation(AssertDefinedColumns.List.class);
		AssertColumnUtils.doAssertion(assertDefinedColumnsList);
	}

}
